﻿using System;
using System.Collections.Concurrent;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace ZOIL.DataModel
{
    public interface ILink
    {
        IPersistable Node1 { get; set; }
        IPersistable Node2 { get; set; }
    }

    /// <summary>
    /// Class to serialize ILink Implementations via XmlSerialize
    /// 
    /// von http://stackoverflow.com/questions/1333864/xml-serialization-of-interface-property
    /// </summary>
    public class LinkXmlSerializable : IXmlSerializable
    {
        public LinkXmlSerializable()
        {

        }

        public LinkXmlSerializable(ILink link)
        {
            Link = link;
        }
        public static LinkXmlSerializable Create(ILink link)
        {
            return new LinkXmlSerializable(link);
        }

        public static ILink ToLink(LinkXmlSerializable link)
        {
            return link.Link;
        }

        public ILink Link { get; set; }

        private static readonly ConcurrentDictionary<Type, XmlSerializer> _xmlSerializersByType = new ConcurrentDictionary<Type, XmlSerializer>();
        private static XmlSerializer GetSerialzerFor(Type t)
        {
            XmlSerializer ser;
            if (!_xmlSerializersByType.TryGetValue(t, out ser))
            {
                ser = new XmlSerializer(t);
                _xmlSerializersByType.TryAdd(t, ser);
            }
            return ser;
        }

        #region Implementation of IXmlSerializable

        /// <summary>
        /// This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
        /// </returns>
        public XmlSchema GetSchema()
        {
            return null;
        }

        /// <summary>
        /// Generates an object from its XML representation.
        /// </summary>
        /// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized. </param>
        public void ReadXml(XmlReader reader)
        {
            if (!reader.HasAttributes)
                throw new FormatException("expected a type attribute!");
            string type = reader.GetAttribute("type");
            reader.Read(); // consume the value
            if (type == "null")
                return;// leave T at default value
            Type t;
            if (!TypeFinder.TryFindType(type, out t))
            {
                return;
            }
            var serializer = GetSerialzerFor(t);
            Link = (ILink)serializer.Deserialize(reader);
            reader.ReadEndElement();
        }

        /// <summary>
        /// Converts an object into its XML representation.
        /// </summary>
        /// <param name="writer">The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized. </param>
        public void WriteXml(XmlWriter writer)
        {
            if (Link == null)
            {
                writer.WriteAttributeString("type", "null");
                return;
            }
            Type type = Link.GetType();
            var serializer = GetSerialzerFor(type);
            writer.WriteAttributeString("type", type.FullName);
            serializer.Serialize(writer, Link);
        }

        #endregion
    }
}